package br.com.kees.persistencia;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import br.com.kees.exception.KeesException;

public abstract class Dao<T> implements Serializable {
	private static final long serialVersionUID = 6176187069298051614L;

	protected String entityClassName;

	@SuppressWarnings("rawtypes")
	public Dao(Class entityClass) {
		entityClassName = entityClass.getName();
	}
	
	public T edit(T obj) throws KeesException {
		EntityManager em = JPAUtil.getEntityManager();
		try {
			em.getTransaction().begin();
			obj = em.merge(obj);
			em.flush();
			em.getTransaction().commit();
			return obj;
		} catch (Exception ex){
			ex.printStackTrace();
			em.getTransaction().rollback();
			throw new KeesException(ex);
			//TODO: trocar o return null por throw new ESException
			//return null;
		} finally {
			JPAUtil.closeEntityManager(em);
		}
	}
	
	public T save(T obj) {
		EntityManager em = JPAUtil.getEntityManager();
		try {
			em.getTransaction().begin();
			em.persist(obj);
			em.getTransaction().commit();
			return obj;
		} catch (Exception ex) {
			ex.printStackTrace();
			em.getTransaction().rollback();
			return obj;
		} finally {
			JPAUtil.closeEntityManager(em);
		}
	}

	public boolean delete(T obj){
		EntityManager em = JPAUtil.getEntityManager();
		try {
			em.getTransaction().begin();
			obj = em.merge(obj);
			em.remove(obj);
			em.getTransaction().commit();
			return true;
		} catch (Exception ex){
			ex.printStackTrace();
			em.getTransaction().rollback();
			return false;
		} finally {
			JPAUtil.closeEntityManager(em);
		}
	}

	@SuppressWarnings("unchecked")
	public T getId(Integer integer) throws KeesException{
		EntityManager em = JPAUtil.getEntityManager();
		try {
			return (T) em.createQuery("select object(c) from " + entityClassName + " as c where c.id = :id" )
			.setParameter("id", integer)
			.getSingleResult();
		} catch (NoResultException e) {
			throw new KeesException("nenhumRegistroEncontrado");
		} catch(PersistenceException e) {
			throw new KeesException(e);
		} finally {
			JPAUtil.closeEntityManager(em);
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll() throws KeesException {
		EntityManager em = JPAUtil.getEntityManager();
		try {
			Query listQuery = em.createQuery("select object(c) from "+ entityClassName +" as c");
			return listQuery.getResultList();
		} catch(Exception e) {
			throw new KeesException(e);
		} finally {
			JPAUtil.closeEntityManager(em);
		}
	}
	
	@SuppressWarnings("rawtypes")
	public List<T> getByObject(T entity, Class[] classChildFetch, String... orderBy) throws KeesException {
		if(entity != null) {
			StringBuilder sql = new StringBuilder();
			String nickEntity = getNickEntity(entity.getClass());
			sql.append("SELECT ").append(nickEntity).append(" FROM ").append(entity.getClass().getCanonicalName()).append(" ").append(nickEntity).append(" ");
			
			if(classChildFetch != null) {
				for(Class obj : classChildFetch) {
					sql.append(appendLeftFetch(entity.getClass(), obj));
				}
			}
			
			return getByObject(entity, nickEntity, "", sql, 1, new HashMap<Integer, Object>(), orderBy);
		} else {
			// TODO: colocar Mensagem de entidade Nula;
			return null;
		}
	}

	@SuppressWarnings("unchecked")
	private List<T> getByObject(T entity, String nickEntity, String foreignKey, StringBuilder sqlBody, Integer countCallMethod, Map<Integer, Object> mapParameters, String... orderBy) throws KeesException {
		EntityManager em = null;
		
		try {
			Field attributes[] = entity.getClass().getDeclaredFields();
			foreignKey = foreignKey.isEmpty() ? "" : foreignKey.concat(".");
			for (int i = 0; i < attributes.length; i++) {
				Field attribute = attributes[i];
				attribute.setAccessible(true);
				
				if(attribute.get(entity) != null && !(Modifier.isStatic(attribute.getModifiers()) && Modifier.isFinal(attribute.getModifiers()))) {
					if(attribute.getAnnotation(javax.persistence.ManyToOne.class) == null) {
						if((attribute.getType() == String.class && !((String)attribute.get(entity)).isEmpty()) || (attribute.getType() == Long.class && ((Long)attribute.get(entity)).intValue() > 0)) {
							sqlBody.append(mapParameters.size() > 0 ? "AND " : "WHERE ");
							mapParameters.put(mapParameters.size()+1, attribute.get(entity));
							
		                	if(attribute.getType() == String.class) {
		                		sqlBody.append("LOWER(").append(nickEntity).append(".").append(foreignKey).append(attribute.getName()).append(") LIKE LOWER('%' || ?")
		                		.append(mapParameters.size()).append(" || '%') ");
		                	} else {
		                		sqlBody.append(nickEntity).append(".").append(foreignKey).append(attribute.getName()).append(" = ?").append(mapParameters.size()).append(" ");
		                	}
						}
					} else {
						getByObject((T) attribute.get(entity), nickEntity, attribute.getName(), sqlBody, countCallMethod+1, mapParameters, orderBy);
					}
				}
			}
			if(countCallMethod == 1) {
				if(orderBy.length > 0) {
					sqlBody.append("ORDER BY ");
					for(int i = 0 ; i < orderBy.length ; i++) {
						sqlBody.append(nickEntity).append(".").append(orderBy[i]);
						
						if(i < orderBy.length - 1) {
							sqlBody.append(", ");
						}
					}
				}
				
				em = JPAUtil.getEntityManager(); 
				Query query = em.createQuery(sqlBody.toString());
					
				for(Entry<Integer, Object> entry : mapParameters.entrySet()) {
					query.setParameter(entry.getKey(), entry.getValue());
				}
				return query.getResultList();
			}
			return null;
			
		} catch (Exception e) {
			throw new KeesException(e);
		} finally {
			JPAUtil.closeEntityManager(em);
		}
	}
	
	@SuppressWarnings("rawtypes")
	public String appendLeftFetch(Class entity, Class... entityChild) {
		StringBuilder builder = new StringBuilder();
		String nickEntity = getNickEntity(entity);
		
		for(Class classe : entityChild) {
			String str = getNickEntity(classe);
			builder.append("LEFT JOIN FETCH ").append(nickEntity).append(".").append(str).append(" ");
		}
		
		return builder.toString();
	}
	
	@SuppressWarnings("rawtypes")
	private String getNickEntity(Class entity) {
		return String.valueOf(entity.getSimpleName().charAt(0)).toLowerCase().concat(entity.getSimpleName().substring(1));
	}
}